package io.gitee.zhangbinhub.admin.gateway.filter

import io.gitee.zhangbinhub.acp.boot.http.HttpStatus
import io.gitee.zhangbinhub.acp.boot.log.LogAdapter
import io.gitee.zhangbinhub.acp.core.common.CommonTools
import io.gitee.zhangbinhub.admin.gateway.bus.publish.RouteLogEventPublish
import io.gitee.zhangbinhub.admin.gateway.constant.GateWayConstant
import io.gitee.zhangbinhub.admin.gateway.log.RouteLogMessage
import io.gitee.zhangbinhub.admin.gateway.log.RouteLogService
import io.opentracing.Span
import io.opentracing.Tracer
import io.opentracing.propagation.Format
import io.opentracing.propagation.TextMapAdapter
import io.opentracing.tag.Tags
import org.noear.solon.Utils
import org.noear.solon.annotation.Component
import org.noear.solon.cloud.gateway.CloudGatewayFilter
import org.noear.solon.cloud.gateway.exchange.ExContext
import org.noear.solon.cloud.gateway.exchange.ExFilterChain
import org.noear.solon.core.exception.StatusException
import org.noear.solon.core.handle.Context
import org.noear.solon.rx.Completable


@Component
class CloudGatewayLogFilter(
    private val logAdapter: LogAdapter,
    private val routeLogEventPublish: RouteLogEventPublish,
    private val routeLogService: RouteLogService,
    private val tracer: Tracer
) : CloudGatewayFilter {
    override fun doFilter(exContext: ExContext, chain: ExFilterChain): Completable =
        buildLog(exContext).let { routeLogMessage ->
            val span = buildSpan(exContext.toContext())
            tracer.activateSpan(span)
            exContext.newRequest().headerAdd("X-B3-TraceId", span.context().toTraceId())
            exContext.newRequest().headerAdd("X-B3-SpanId", span.context().toSpanId())
            exContext.newRequest().headerAdd("X-B3-ParentSpanId", span.context().toSpanId())
            exContext.newRequest().headerAdd("X-B3-Sampled", "1")
            routeLogMessage.traceId = span.context().toTraceId()
            doFilter(exContext, chain, routeLogMessage, span)
        }

    private fun doFilter(
        exContext: ExContext,
        chain: ExFilterChain,
        routeLogMessage: RouteLogMessage,
        span: Span
    ): Completable = chain.doFilter(exContext).doOnComplete {
        // 正常响应
        responseLog(exContext, routeLogMessage)
        span.finish()
    }.doOnError { err ->
        logAdapter.error(err.message, err)
        // 响应出错
        if (err is StatusException) {
            exContext.newResponse().status(err.code)
        } else {
            exContext.newResponse().status(HttpStatus.INTERNAL_SERVER_ERROR.value())
        }
        responseLog(exContext, routeLogMessage)
        span.log(Utils.throwableToString(err)).finish()
    }

    /**
     * 构建路由日志消息
     */
    private fun buildLog(exContext: ExContext): RouteLogMessage {
        exContext.newRequest().apply {
            this.headerAdd(GateWayConstant.GATEWAY_HEADER_REQUEST_TIME, System.currentTimeMillis().toString())
            this.headerAdd(GateWayConstant.GATEWAY_HEADER_REQUEST_ID, CommonTools.getUuid32())
        }
        return routeLogService.createRouteLogMessage(exContext)
    }

    /**
     * 响应日志
     */
    private fun responseLog(exContext: ExContext, routeLogMessage: RouteLogMessage) {
        val responseTime = System.currentTimeMillis()
        routeLogMessage.processTime = responseTime - routeLogMessage.requestTime
        routeLogMessage.responseTime = responseTime
        routeLogMessage.responseStatus = exContext.newResponse().status
        val requestNew = exContext.newRequest()
        val targetNew = exContext.targetNew()
        routeLogMessage.targetIp = targetNew.host
        routeLogMessage.targetUri = targetNew.toString()
        routeLogMessage.targetPath = requestNew.path
        if (routeLogMessage.targetPath == GateWayConstant.TARGET_APPLY_TOKEN_PATH) {
            routeLogMessage.applyToken = true
        }
        if (routeLogMessage.applyToken) {
            routeLogService.resolveResponseToken(routeLogMessage, exContext, routeLogEventPublish)
        } else {
            routeLogEventPublish.doRouteLog(routeLogMessage)
        }
    }

    fun buildSpan(ctx: Context): Span {
        //实例化构建器
        val operationName = StringBuilder()
        operationName.append(ctx.pathNew()).append(" (").append(ctx.uri().scheme).append(" ").append(ctx.method())
            .append(')')
        var spanBuilder = tracer.buildSpan(operationName.toString())

        //添加种类标志
        spanBuilder.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
        spanBuilder.withTag("req.url", ctx.url())

        //获取上下文
        val headerMapAdapter = TextMapAdapter(ctx.headerMap().toValueMap())

        val spanContext = tracer.extract(Format.Builtin.HTTP_HEADERS, headerMapAdapter)
        if (spanContext != null) {
            //如果有，说明是从上传传导过来的
            spanBuilder = spanBuilder.asChildOf(spanContext)
        }

        val span = spanBuilder.start()

        //尝试注入
        tracer.inject(span.context(), Format.Builtin.HTTP_HEADERS, headerMapAdapter)

        //开始
        return span
    }
}